package com.studiofortress.sf.util.structure;

import com.studiofortress.sf.graphics.display.ControlClickEvent;
import com.studiofortress.sf.graphics.display.Controls;
import com.studiofortress.sf.graphics.GraphicsGL;
import com.studiofortress.sf.structure.Actor;
import com.studiofortress.sf.structure.World;
import com.studiofortress.sf.structure.WorldControls;

/**
 * This is a lightweight button as an Actor. It will look for the mouse and
 * check it in relation to itself on each act. If there is a state change
 * (it's highlighted, pressed or raised) then the appropriate method is called.
 *
 * When deciding between different states (i.e. for deciding which state to
 * draw) the pressed is checked first, then the highlighted state and then the
 * raised state is used as the default state.
 * 
 * The methods onHighlight, onPress and onRaise do nothing by default and should
 * be overriden in order to have code run when those states occur.
 *
 * The event handler switch allows you to switch between this using the
 * getControls() primitives for detecting input, or handleClick. The advantage
 * in turning it on is that you can place something on top that can grab the
 * clicks, which this can then ignore. You can't do this easily with the event
 * handling turned off.
 *
 * Warning! For the event handling to work it must also be turned on in the
 * GraphicsDisplay.
 * 
 * @author Joseph Lenton
 */
public abstract class ButtonActor<G extends GraphicsGL> extends Actor<G>
{
    // used to detect the state of the button
    private boolean down;
    private boolean highlight;

    private WorldControls controls;
    private boolean useEventHandler;
    
    // used to signify that the event handler has detected a mouse press
    private boolean eventHandlerDown;
    
    public ButtonActor()
    {
        this( false );
    }
    
    public ButtonActor(boolean useEventHandler)
    {
        controls = null;
        down = false;
        highlight = false;
        this.useEventHandler = useEventHandler;
    }
    
    /**
     * Allows you to turn on and off this ButtonActor using it's handleClickEvent
     * method for detecting clicks.
     * @param useEventHandler True to turn this on, false to turn it off.
     */
    public void setEnableEventHandling(boolean useEventHandler)
    {
        this.useEventHandler = useEventHandler;
    }

    /**
     * For detecting if this is or isn't using the handleClickEvent method for
     * detecting clicks on this Button.
     * @return True if it is using the handleClickEvent, false if it's just using getControls().
     */
    public boolean isUsingEventHandler()
    {
        return useEventHandler;
    }

    /**
     * By default this does nothing. Override this to add functionality to be
     * called when the button is raised.
     */
    protected void onRaise() { }
    
    /**
     * By default this does nothing. Override this to add functionality to be
     * called when the button is pressed down.
     */
    protected void onPress() { }
    
    /**
     * By default this does nothing. Override this to add functionality to be
     * called when the button is highlighted.
     */
    protected void onHighlight() { }
    
    /**
     * By default this method does nothing. Override this to add functionality
     * to be called when the button is no longer highlighted.
     */
    protected void onHighlightLoss() { }
    
    /**
     * @return True if the mouse is currently over this Actor and is highlighting it. Otherwise false.
     */
    public boolean isHighlighted()
    {
        return highlight;
    }
    
    /**
     * @return True if the mouse has pressed this button down, otherwise false if it is up.
     */
    public boolean isPressed()
    {
        return down;
    }
    
    /**
     * This is the test to state if the mouse is currently over the Button or
     * not. If this returns false then this component is not being highlighted
     * or pressed. It can only be raised.
     * @return True if the mouse is currently over this component, otherwise false.
     */
    protected boolean isMouseOver()
    {
        if (controls == null) {
            World world = getWorld();
            if (world != null) {
                controls = world.getControls();
            } else {
                return false;
            }
        }

        final int mouseX = controls.getWorldMouseX();
        final int mouseY = controls.getWorldMouseY();

        final int halfWidth = getWidth()/2;
        final int halfHeight = getHeight()/2;
        int x = (int)getX();
        int y = (int)getY();

        return mouseX >= x-halfWidth && mouseX <= x+halfWidth &&
            mouseY >= y-halfHeight && mouseY <= y+halfHeight;
    }
    
    /**
     * This calls actButton. If this is overridden then either super.act
     * or actButton will need to be called in order to update this buttons
     * information.
     */
    @Override
    public void act()
    {
        actButton();
    }
    
    /**
     * Updates this buttons information. By default this is performed by the
     * act method in the ButtonActor class. This is simply here to allow users
     * to update this button if it cannot be done by calling the act method.
     */
    public void actButton()
    {
        if (isMouseOver())
        {
            // mouse just moved onto button
            if (!highlight) {
                onHighlight();
                highlight = true;
            }

            // mouse is on button and down
            if ( !useEventHandler ) {
                if ( controls.isMouseClick(Controls.MOUSE_LEFT) ) {
                    if (!down) {
                        down = true;
                        onPress();
                    }
                // mouse is on button and up
                } else if (down) {
                    down = false;
                    onRaise();
                }
            // it's been clicked via the event handler
            } else if ( eventHandlerDown ) {
                if ( down ) {
                    eventHandlerDown = false;
                    down = false;
                    onRaise();
                } else {
                    down = true;
                    onPress();
                }
            }
        // mouse is not on the button, so raise
        } else {
            if (highlight) {
                highlight = false;
                onHighlightLoss();
            }
            
            down = false;
            onRaise();
        }
    }

    @Override
    protected void handleClickEvent(ControlClickEvent event)
    {
        if ( useEventHandler  &&
             event.getMouseButton() == Controls.MOUSE_LEFT &&
             getWorld().getControls().isActorUnderMouse(this) )
        {
            event.setHandled();
            eventHandlerDown = true;
        }
    }
    
    /**
     * Simply calls the paintButton method with the GraphicsGL given.
     * @param g
     */
    @Override
    public void paint(G g)
    {
        paintButton(g);
    }

    /**
     * This is the standard button drawing logic code. It calls the appropriate
     * drawing method (paintPressed, paintRaised or paintHighlighted) with the
     * GraphicsGL given.
     *
     * This is implemented here to allow the user to call this logic themselves
     * if required.
     * @param g The GraphicsGL object this will draw this button too.
     */
    protected void paintButton(G g)
    {
        if (isPressed()) {
            paintPressed(g);
        } else if (isHighlighted()) {
            paintHighlighted(g);
        } else {
            paintRaised(g);
        }
    }

    /**
     * This is called by the paintButton method to draw the code that displays
     * this button as pressed. By default this draws nothing.
     * @param g The GraphicsGL object this will draw too.
     */
    protected void paintPressed(G g) { }

    /**
     * This is called by the paintButton method to draw the code that displays
     * this button in it's raised state. By default this draws nothing.
     * @param g The GraphicsGL object this will draw too.
     */
    protected void paintRaised(G g) { }

    /**
     * This is called by the paintButton method to draw the code that displays
     * this button when it is highlighted. By default this draws nothing.
     * @param g The GraphicsGL object this will draw too.
     */
    protected void paintHighlighted(G g) { }
}
